//
//  NSString+SFCLibExtensions.m
//  SVNForCocoa
//
//  Created by Jeremy Pereira on 07/11/2013.
//  Copyright (c) 2013 Jeremy Pereira. All rights reserved.
//

#import "SFCLib+Internals.h"
#import "SFCAPRPool.h"

@implementation NSString (SFCLibExtensions)

-(svn_string_t*) svnStringWithPool: (SFCAPRPool*) pool
{
	return [self svnStringWithAprPool: [pool aprPool]];
}

+(NSString*) stringWithSvnString: (const svn_string_t*) svnString
{
    return [[NSString alloc] initWithBytes: svnString->data
                                	length: svnString->len
                                  encoding: NSUTF8StringEncoding];
}

-(svn_string_t*) svnStringWithAprPool: (apr_pool_t*) pool
{
	return svn_string_create([self UTF8String], pool);
}

-(NSString*) absolutePath
{
    NSString* ret = nil;
    if ([self isAbsolutePath])
    {
        ret = [self copy];
    }
    else
    {
        NSFileManager* fm = [NSFileManager defaultManager];
        NSString* currentDirectory = [fm currentDirectoryPath];
        ret = [[currentDirectory stringByAppendingPathComponent: self] stringByStandardizingPath];
    }
    if ([ret hasPrefix: @"/private"])
    {
        ret = [ret substringFromIndex: [@"/private" length]];
    }
    return ret;
}


@end

@implementation NSDictionary (SFCLibExtensions)

+(NSDictionary*) dictionaryWithAprHash: (apr_hash_t*) aprHash
						  keyConverter: (MakeObjectFromSvnType) keyConverter
                        valueConverter: (MakeObjectFromSvnType) valueConverter
{
    NSMutableDictionary* ret = [[NSMutableDictionary alloc] init];
    if (aprHash != NULL)
    {
        apr_pool_t* hashPool = apr_hash_pool_get(aprHash);
        apr_pool_t* scratchPool;
        // TODO: check the error status
        apr_pool_create(&scratchPool, hashPool);
        apr_hash_index_t* iterator = apr_hash_first(scratchPool, aprHash);
        while (iterator != NULL)
        {
            const void* keyPtr = NULL;
            apr_ssize_t keyLength;
            void* valuePtr = NULL;
            apr_hash_this(iterator,
                          (const void**)&keyPtr,
                          &keyLength,
                          (void**)&valuePtr);
            id key = keyConverter(keyPtr, keyLength);
            id value = valueConverter(valuePtr, 0);
            [ret setObject: value forKey: key];
            iterator = apr_hash_next(iterator);
        }
        apr_pool_destroy(scratchPool);
    }
    return ret;
}


+(NSDictionary*) dictionaryWithAprHashStringToSVNString: (apr_hash_t*) aprHash
{
    return [self dictionaryWithAprHash: aprHash
                          keyConverter:^id(const void *keyPtr, size_t size)
    						{
        						return [NSString stringWithUTF8String: (const char *) keyPtr];
                          	}
                        valueConverter:^id(const void *valuePtr, size_t size)
    						{
        						return [NSString stringWithSvnString: (const svn_string_t*) valuePtr];
                        	}];
}

-(apr_hash_t*) aprHashWithKeyConverter: (void (^)(id keyIn, const void** keyOut, apr_ssize_t* keyLength, SFCAPRPool* pool)) keyConverter
                       objectConverter: (void (^)(id objectIn, const void** objectOut, SFCAPRPool* pool)) objectConverter
                                  pool: (SFCAPRPool*) pool
{
    apr_hash_t* ret = apr_hash_make([pool aprPool]);
    [self enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop)
    {
        const void* aprKey;
        apr_ssize_t aprKeyLength;
        const void* aprValue;
		keyConverter(key, &aprKey, &aprKeyLength, pool);
        objectConverter(obj, &aprValue, pool);
        apr_hash_set(ret, aprKey, aprKeyLength, aprValue);
    }];
    return ret;
}

-(apr_hash_t*) aprHashOfUTF8StringsWithPool: (SFCAPRPool*) thePool
{
    return [self aprHashWithKeyConverter:^(id keyIn, const void **keyOut, apr_ssize_t *keyLength, SFCAPRPool *pool)
            {
                *keyOut = [keyIn svnStringWithPool: pool]->data;
                *keyLength = APR_HASH_KEY_STRING;
            }
                                   objectConverter:^(id objectIn, const void **objectOut, SFCAPRPool *pool)
            {
                *objectOut = [objectIn svnStringWithPool: pool];
            }
                                              pool: thePool];
}


@end

@implementation NSArray(SFCLibExtensions)

+(NSArray*) arrayWithAprArray: (const apr_array_header_t*) aprArray
              objectConverter: (MakeObjectFromSvnType) objectConverter
{
    NSMutableArray* ret = [[NSMutableArray alloc] init];
    if (aprArray != NULL)
    {
        size_t size = aprArray->elt_size;
        for (int i = 0 ; i < aprArray->nelts ; ++i)
        {
            id obj = objectConverter(aprArray->elts + i * size, size);
            [ret addObject: obj];
        }
    }
    return ret;
}

-(apr_array_header_t*) aprArrayWithSize: (size_t) elementSize
                        objectConverter: (void (^)(void* destination, id object, SFCAPRPool* pool)) objectConverter
                                   pool: (SFCAPRPool*) pool
{
    apr_array_header_t* ret = apr_array_make([pool aprPool],
                                             (int)[self count],
                                             (int)elementSize);
    for (id element in self)
    {
        objectConverter(apr_array_push(ret), element, pool);
    }
    return ret;
}


-(apr_array_header_t*) aprArrayOfUTF8StringsWithPool: (SFCAPRPool*) pool
{
    return [self aprArrayWithSize: sizeof(const char*)
                  objectConverter:^(void *destination, id object, SFCAPRPool *pool)
    {
        svn_string_t* svnString = [object svnStringWithPool: pool];
        *(const char**)destination = svnString->data;
    }
                             pool: pool];
}

@end

static  svn_error_t* writeSvnStream(void *baton, const char *data, apr_size_t *len);

@implementation NSDate (SFCLibExtensions)

+(NSDate*) dateWithAprTime: (apr_time_t)aprTime
{
    NSTimeInterval timeInterval = (double) aprTime / 1000000;
    return [NSDate dateWithTimeIntervalSince1970: timeInterval];
}

-(apr_time_t) aprTime
{
    return [self timeIntervalSince1970] * 1000000;
}

@end

@implementation NSOutputStream(SFCLibExtensions)

-(svn_stream_t*) svnStreamWithPool: (SFCAPRPool*) pool
{
	svn_stream_t* svnStream = svn_stream_create((__bridge void *)(self), [pool aprPool]);
	svn_stream_set_write(svnStream, writeSvnStream);
    return svnStream;
}

@end

svn_error_t* writeSvnStream(void *baton, const char *data, apr_size_t *len)
{
    NSOutputStream* stream = (__bridge NSOutputStream*) baton;
    apr_size_t totalBytesWritten = 0;
    NSInteger bytesWritten = 0;
    while (totalBytesWritten < *len && bytesWritten != -1)
    {
        bytesWritten = [stream write: (const uint8_t*)data + totalBytesWritten
                           maxLength: *len - totalBytesWritten];
        if (bytesWritten != -1)
        {
			totalBytesWritten += bytesWritten;
        }
    }
    svn_error_t* ret = NULL;
   	if (bytesWritten == -1)
    {
		NSError* theError = [stream streamError];
        if (theError == nil)
        {
			theError = [SFCError errorWithDomain: SFC_ERROR_DOMAIN
                                            code: SFC_ERROR_FILE_WRITE
                            localizedDescription: @"Unable to write to a file"];
        }
        ret = [SFCError svnErrorFromError: theError];
    }
    else
    {
        *len = totalBytesWritten;
    }
    return ret;
}

